package cn.edu.hitwh.kanlema.service.impl;

import cn.edu.hitwh.kanlema.constant.MqConstants;
import cn.edu.hitwh.kanlema.constant.RedisConstants;
import cn.edu.hitwh.kanlema.dto.UserDTO;
import cn.edu.hitwh.kanlema.entity.SeckillVoucher;
import cn.edu.hitwh.kanlema.entity.VoucherOrder;
import cn.edu.hitwh.kanlema.exception.ApiException;
import cn.edu.hitwh.kanlema.mapper.VoucherOrderMapper;
import cn.edu.hitwh.kanlema.message.VoucherOrderMessage;
import cn.edu.hitwh.kanlema.service.ISeckillVoucherService;
import cn.edu.hitwh.kanlema.service.IVoucherOrderService;
import cn.edu.hitwh.kanlema.util.RedisCache;
import cn.edu.hitwh.kanlema.util.UserHolder;
import cn.hutool.core.lang.Snowflake;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author vvcoder
 * @since 2025-8-18
 */
@Service
@Slf4j
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
    @Autowired
    private ISeckillVoucherService seckillVoucherService;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private Snowflake snowflake;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public boolean preloadVouchers() {
        // 获取今天最后一分钟
        LocalDateTime loadTo = LocalDateTime.now().withHour(23).withMinute(59).withSecond(0);
        // 如果当前时间已经超过了今天最后一分钟，那么预加载明天的优惠券
        if (LocalDateTime.now().isAfter(loadTo)) {
            loadTo = loadTo.plusDays(1);
        }
        Duration ttl = Duration.between(LocalDateTime.now(), loadTo);
        if (!Boolean.TRUE.equals(redisTemplate.opsForValue()
                .setIfAbsent(RedisConstants.CACHE_VOUCHER_PRELOAD_FLAG_KEY, "1", ttl))) {
            return false;
        }

        try {
            // 预加载优惠券
            doPreloadVouchers(loadTo);
        } catch (Exception e) {
            redisCache.delete(RedisConstants.CACHE_VOUCHER_PRELOAD_FLAG_KEY);
            log.error("预加载优惠券失败", e);
        }
        return false;
    }

    private void doPreloadVouchers(LocalDateTime loadTo) {
        // 实际多加载10分钟，防止遗漏优惠券
        LocalDateTime actuallyLoadTo = loadTo.plusMinutes(10);
        LocalDateTime now = LocalDateTime.now();
        List<SeckillVoucher> seckillVouchers = seckillVoucherService.list(
                Wrappers.<SeckillVoucher>lambdaQuery()
                        .le(SeckillVoucher::getBeginTime, actuallyLoadTo)
                        .ge(SeckillVoucher::getEndTime, now)
        );

        for (SeckillVoucher seckillVoucher : seckillVouchers) {
            cacheVoucher(seckillVoucher);
        }
    }

    public void cacheVoucher(SeckillVoucher seckillVoucher) {
        // 构建优惠券缓存
        Map<String, String> voucherMap = new HashMap<>();
        voucherMap.put(RedisConstants.CACHE_VOUCHER_STOCK_HKEY, seckillVoucher.getStock().toString());
        voucherMap.put(RedisConstants.CACHE_VOUCHER_BEGIN_TIME_HKEY, seckillVoucher.getBeginTime().toString());
        voucherMap.put(RedisConstants.CACHE_VOUCHER_END_TIME_HKEY, seckillVoucher.getEndTime().toString());
        redisTemplate.opsForHash().putAll(RedisConstants.CACHE_VOUCHER_KEY + seckillVoucher.getVoucherId(), voucherMap);
        // 设置优惠券缓存过期时间
        LocalDateTime now = LocalDateTime.now();
        Duration ttl = Duration.between(now, seckillVoucher.getEndTime());
        redisTemplate.expire(RedisConstants.CACHE_VOUCHER_KEY + seckillVoucher.getVoucherId(), ttl.toNanos(), TimeUnit.NANOSECONDS);
        try {
            // 已经开始的活动，缓存已购买用户id
            if (seckillVoucher.getBeginTime().isBefore(now)) {
                // 缓存已购买用户id
                String[] userIds = lambdaQuery()
                        .select(VoucherOrder::getUserId)
                        .eq(VoucherOrder::getVoucherId, seckillVoucher.getVoucherId())
                        .list()
                        .stream()
                        .map(VoucherOrder::getUserId)
                        .map(Object::toString)
                        .toArray(String[]::new);
                if (userIds.length > 0) {
                    // 缓存已购买用户id
                    redisTemplate.opsForSet().add(RedisConstants.CACHE_VOUCHER_USER_KEY + seckillVoucher.getVoucherId(), userIds);
                    // 设置已购买用户id缓存过期时间
                    redisTemplate.expire(
                            RedisConstants.CACHE_VOUCHER_USER_KEY + seckillVoucher.getVoucherId(),
                            Duration.between(now, seckillVoucher.getEndTime()).toMinutes() + 10,
                            TimeUnit.MINUTES
                    );
                }
            }
        } catch (Exception e) {
            // 防止缓存不一致造成非法订单
            redisCache.delete(RedisConstants.CACHE_VOUCHER_KEY + seckillVoucher.getVoucherId());
            log.error("缓存优惠券已购买用户id失败", e);
        }
    }

    private SeckillVoucher getCachedVoucher(Long voucherId) {
        Map<Object, Object> voucherMap = redisTemplate.opsForHash().entries(RedisConstants.CACHE_VOUCHER_KEY + voucherId);
        if (voucherMap.isEmpty()) {
            return null;
        }
        SeckillVoucher seckillVoucher = new SeckillVoucher();
        seckillVoucher.setVoucherId(voucherId);
        seckillVoucher.setStock(Integer.parseInt((String) voucherMap.get(RedisConstants.CACHE_VOUCHER_STOCK_HKEY)));
        seckillVoucher.setBeginTime(LocalDateTime.parse((String) voucherMap.get(RedisConstants.CACHE_VOUCHER_BEGIN_TIME_HKEY)));
        seckillVoucher.setEndTime(LocalDateTime.parse((String) voucherMap.get(RedisConstants.CACHE_VOUCHER_END_TIME_HKEY)));
        return seckillVoucher;
    }

    @Override
    public Long seckillVoucher(Long voucherId) {
        UserDTO user = UserHolder.getUser();
        Long userId = user.getId();

        // 检查优惠券是否有效
        SeckillVoucher voucher = getCachedVoucher(voucherId);
        if (voucher == null) {
            throw new ApiException("活动未开始或已经结束");
        }
        LocalDateTime now = LocalDateTime.now();
        if (now.isBefore(voucher.getBeginTime())) {
            throw new ApiException("活动未开始");
        }
        if (now.isAfter(voucher.getEndTime())) {
            throw new ApiException("活动已结束");
        }

        // 检查用户是否已经购买过该优惠券
        if (Objects.equals(0L, redisTemplate.opsForSet()
                .add(RedisConstants.CACHE_VOUCHER_USER_KEY + voucherId, userId.toString()))) {
            throw new ApiException("用户已经购买过该优惠券");
        } else {
            redisTemplate.expire(
                    RedisConstants.CACHE_VOUCHER_USER_KEY + voucherId,
                    Duration.between(now, voucher.getEndTime()).toMinutes() + 10,
                    TimeUnit.MINUTES
            );
        }

        // 尝试预扣减库存
        Long stock = redisTemplate.opsForHash().increment(
                RedisConstants.CACHE_VOUCHER_KEY + voucherId,
                RedisConstants.CACHE_VOUCHER_STOCK_HKEY,
                -1
        );
        if (stock < 0) {
            throw new ApiException("优惠券已被抢完");
        }

        // 生成订单id
        Long orderId = snowflake.nextId();

        // 构建订单消息
        VoucherOrderMessage message = new VoucherOrderMessage();
        message.setOrderId(orderId);
        message.setVoucherId(voucherId);
        message.setUserId(userId);

        // 发送订单消息到MQ
        rabbitTemplate.convertAndSend(MqConstants.ORDER_EXCHANGE, MqConstants.ORDER_VOUCHER_KEY, message);

        // 返回订单id
        return orderId;
    }

    @Override
    @Transactional
    public void createVoucherOrder(VoucherOrderMessage message) {
        // 检查订单是否已经存在
        Long orderId = message.getOrderId();
        if (Objects.nonNull(lambdaQuery().eq(VoucherOrder::getId, orderId).one())) {
            // 幂等
            return;
        }
        // 扣减库存
        if (!seckillVoucherService.deductStock(message.getVoucherId())) {
            log.error("扣减库存失败，优惠券：{}", message);
            return;
        }
        // 创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        voucherOrder.setId(orderId);
        voucherOrder.setVoucherId(message.getVoucherId());
        voucherOrder.setUserId(message.getUserId());
        voucherOrder.setStatus(1);
        save(voucherOrder);
    }
}
